﻿
using FarseerGames.FarseerPhysics;
using GraphicalSVN.GraphicSource;
using System.Windows.Controls;
using Border = GraphicalSVN.WPFApp.Border;
using System.Windows;
using System.Windows.Shapes;
using FarseerGames.FarseerPhysics.Dynamics;
using System.Windows.Media;
using FarseerGames.FarseerPhysics.Mathematics;
using FarseerGames.FarseerPhysics.Dynamics.Springs;
using System;
using FarseerGames.FarseerPhysics.Factories;
using System.Windows.Input;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Dynamics.Joints;
using System.Linq;
using System.Threading;
using System.ComponentModel;
using System.Windows.Threading;
using log4net;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using GraphicalSVN.WPFApp;
using GraphicalSVN.GraphicSource.Dto;
using System.Diagnostics;

namespace GraphicalSVN.WPFApp
{
    public class Generator : Canvas
    {
        private bool isPaused;
        private GameLoop gameLoop;
        private PhysicsSimulator physicsSimulator;
        private Border border;
        GeneratorInfo gi = new GeneratorInfo();
        private float xSeedPosition = 0;
        private float ySeedPosition = 0;
        private IGraphicalSource graphicalSource;
        private BackgroundWorker background = new BackgroundWorker();
        private readonly ILog logger = LogManager.GetLogger(typeof(IGraphicalSource));
        private SVNFrameData globalFrameData = new SVNFrameData();
        private Dictionary<string, FrameworkElement> frameworkSpringItems;
        private Dictionary<string, FrameworkElement> frameworkBodyItems;
        private Date currentDate;
        private Identity identity;
        private Random random = new Random();
        List<Body> worldBodies = new List<Body>();
        Queue<string> disposeList = new Queue<string>();
        public Generator()
        {
            Settings cfg = Settings.Default;
            gi = new GeneratorInfo()
            {
                Repository = cfg.Repository,
                SecPerDay = cfg.SecondsPerDay,
                FPS = cfg.FPS,
                WorkingDirectory = cfg.WorkingDirectory,
                Width = cfg.Width,
                Height = cfg.Height,
                ShowDirectoryNames = false,
                Filter = cfg.Ignore
            };
            StructureMapConfiguration.Setup.ForGenerator(gi);
            frameworkSpringItems = new Dictionary<string, FrameworkElement>();
            frameworkBodyItems = new Dictionary<string, FrameworkElement>();
            graphicalSource = StructureMapConfiguration.Setup.GetInstance<IGraphicalSource>();
            //graphicalSource.Generate();
            ThreadPool.QueueUserWorkItem(DoBackground);

            Loaded += OnLoaded;
        }

        private void Initialise()
        {
            ClearCanvas();
            physicsSimulator = new PhysicsSimulator(new Vector2(0, -100f));
            //add border
            int borderWidth = (int)(ScreenHeight * .05f);
            border = new Border(ScreenWidth, ScreenHeight, borderWidth,
                                 ScreenCenter);
            border.Load(this, physicsSimulator);
            xSeedPosition = (float)gi.Width / 2;
            ySeedPosition = (float)gi.Height - borderWidth - 15f;
        }

        void DoBackground(object sender)
        {
            graphicalSource.Generate();
        }

        void OnLoaded(object sender, RoutedEventArgs e)
        {
            Initialise();
        }

        public void SetGameLoop(GameLoop gameLoop)
        {
            if (this.gameLoop != null)
            {
                this.gameLoop.Update -= GameLoopOnUpdate;
            }

            this.gameLoop = gameLoop;

            if (this.gameLoop != null)
            {
                this.gameLoop.Update += GameLoopOnUpdate;
            }
        }

        public void SetCurrentDate(Date currentDate)
        {
            this.currentDate = currentDate;
        }

        public void SetCurrentIdentity(Identity identity)
        {
            this.identity = identity;
        }

        private void GameLoopOnUpdate(TimeSpan span)
        {
            if (!IsVisible) return;
            if (IsPaused) return;

            if (physicsSimulator != null)
            {
                Update(span);
            }
        }

        private bool OnCollision(Geom geom1, Geom geom2, ContactList contactList)
        {
            return true;
        }

        private void OnSeperation(Geom geom1, Geom geom2)
        {
        }

        private bool OnBroadPhaseCollision(Geom geom1, Geom geom2)
        {
            return true;
        }

        protected virtual void Update(TimeSpan span)
        {
            lock (this)
            {
                //ThreadPool.QueueUserWorkItem(DoUpdate);
                TimeSpan newspan = new TimeSpan(1);
                globalFrameData = graphicalSource.Update(newspan);
                UpdatePhysicsSimulator(globalFrameData);
                this.currentDate.Update(globalFrameData.RevisionDate);
                physicsSimulator.Update((float)span.TotalSeconds);
            }
        }

        void DoUpdate(object status)
        {
            TimeSpan span = new TimeSpan(1);
            globalFrameData = graphicalSource.Update(span);
        }

        private void UpdatePhysicsSimulator(SVNFrameData frameData)
        {
            try
            {
                CreateObjects(frameData);
            }
            catch (Exception ex)
            {
                logger.Error(ex.Message);
                throw ex;
            }

            try
            {
                logger.Debug("BodyCount:" + physicsSimulator.BodyList.Count);
                //Debug.WriteLineIf(physicsSimulator.BodyList.Count > 1, physicsSimulator.BodyList.Count.ToString(), "BodyCount");
                //Debug.Print("BodyCount:");
                //Debug.Print(physicsSimulator.BodyList.Count.ToString());
                foreach (var body in physicsSimulator.BodyList.Where(x => x.Tag != null && !x.Tag.ToString().StartsWith("B:") && !x.Tag.ToString().StartsWith("P:")))
                {
                    bool exists = CheckObjectExistsInWorldAndHasJoints(body);
                    //Debug.WriteLineIf(!exists, "Does not Exist", body.Tag.ToString());
                    if (!exists)
                    {
                        logger.Debug("Doesnt exist remove stuff:" + body.Tag.ToString());
                        RemoveBodyJointsAndSprings(body);
                    }
                }
                RemoveBodies();
            }
            catch (Exception ex)
            {
                logger.Error(ex.Message);
                throw ex;
            }
        }

        private void RemoveBodies()
        {
            while (disposeList.Count > 0)
            {
                var listItem = disposeList.Dequeue();
                // Debug.WriteLine(listItem, "RemoveBodies");
                var body = physicsSimulator.BodyList.Where(x => string.Equals(listItem, x.Tag.ToString())).SingleOrDefault();
                if (body != null)
                {
                    body.IsDisposed = true;
                }

                //Debug.WriteLine(listItem, "RemoveVisual");
                FrameworkElement bodyVisual = frameworkBodyItems.Where(y => string.Equals(y.Key, listItem)).SingleOrDefault().Value;
                if (bodyVisual != null)
                {
                    RemoveVisual(bodyVisual);
                }
            }
        }

        private void RemoveBodyJointsAndSprings(Body body)
        {
            var geom = physicsSimulator.GeomList.Where(x => string.Equals(x.Tag, body.Tag)).SingleOrDefault();
            if (geom != null)
            {
                geom.IsDisposed = true;
                body.Mass = 0.001f;
            }

            physicsSimulator.SpringList
                .Where(x => x.Tag.ToString().StartsWith(body.Tag.ToString() + ":") || x.Tag.ToString().EndsWith(body.Tag.ToString())).ToList()
                .ForEach(x =>
            {
                x.Dispose();
                FrameworkElement springVisual = frameworkSpringItems.Where(y => string.Equals(y.Key, x.Tag.ToString())).SingleOrDefault().Value;
                RemoveVisual(springVisual);
            });

            var joints = physicsSimulator.JointList
                .Where(x => x.Tag.ToString().StartsWith(body.Tag.ToString() + ":") || x.Tag.ToString().EndsWith(body.Tag.ToString())).ToList();
            joints.ForEach(x =>
            {
                new Timer(work, body.Tag.ToString(), 0, -1);
                //body.IsDisposed = true;
                ((SliderJoint)x).Min = 0f;
                ((SliderJoint)x).Max = 0f;
            });
        }

        private void work(object body)
        {
            lock (this)
            {
                string bodyVis = (string)body;
                disposeList.Enqueue(bodyVis);
            }
        }

        private bool CheckObjectExistsInWorldAndHasJoints(Body body)
        {
            logger.Debug("Does Body exist:" + body.Tag.ToString());
            bool exists = false;
            foreach (var item in globalFrameData.Items.Where(x => x.IsDirectory))
            {
                var type = item.IsDirectory ? "D:" : "F:";
                if (body.Tag.ToString() == type + item.ObjectPath)
                {
                    logger.Debug("body found:" + item.ObjectPath);
                    exists = true;
                    logger.Debug("Total Joints:" + physicsSimulator.JointList.Where(x => x.Tag == null).Count());
                    var joints = physicsSimulator.JointList.Where(
                        x => x.Tag != null && x.Tag.ToString().StartsWith(type + item.ObjectPath + ":")
                            ||
                        x.Tag.ToString().EndsWith(":" + type + item.ObjectPath)).Count();
                    logger.Debug("joints" + joints);
                    joints += physicsSimulator.SpringList.Where(
                        x => x.Tag != null && x.Tag.ToString().StartsWith(type + item.ObjectPath + ":")
                            ||
                        x.Tag.ToString().EndsWith(":" + type + item.ObjectPath)).Count();

                    logger.Debug("Joints Calculated");

                    if (item.ObjectPath == item.ParentDirectory)
                    {
                        joints++;
                    }

                    logger.Debug("Joints Found:" + joints);

                    if (joints == 0)
                    {
                        FrameworkElement bodyVis = frameworkBodyItems.Where(x => x.Key == body.Tag.ToString()).SingleOrDefault().Value;
                        RemoveVisual(bodyVis);
                    }
                    break;
                }
            }
            return exists;
        }

        private void CreateObjects(SVNFrameData frameData)
        {
            foreach (var item in frameData.Items.Where(x => x.IsDirectory))
            {
                var type = item.IsDirectory ? "D:" : "F:";
                bool exists = SVNObjectExists(item, type);
                //Debug.WriteLineIf(exists, item.ObjectPath,"Exists:");
                if (!exists)
                {
                    //Debug.WriteLineIf(!exists, item.ObjectPath, "Not Exist");
                    if (item.ObjectPath == item.ParentDirectory && item.IsDirectory)
                    {
                        CreateParent(item);
                    }
                    else
                    {
                        var parent = worldBodies.Where(x => x.Tag.ToString() == "D:" + item.ParentDirectory).SingleOrDefault();
                        if (parent == null)
                        {
                            CreateParent(item);
                        }
                        else
                        {
                            if (item.IsDirectory)
                            {
                                //Debug.WriteLine(item.ObjectPath, "Create Directory");
                                worldBodies.Add(CreateDirectoryBodySpringAndGravity(parent, item));
                            }
                            //else
                            //{
                            //    worldBodies.Add(CreateFileBody(parent, item));
                            //}
                        }
                    }

                }
            }
        }

        private void CreateParent(SVNObject item)
        {
            List<Body> bodies = new List<Body>();
            var parents = physicsSimulator.BodyList.Where(x => x.IsStatic == true && x.Tag.ToString().StartsWith("D:")).ToList();
            foreach (var parent in parents)
            {
                parent.IsStatic = false;
            }

            var _parent = BodyFactory.Instance.CreateCircleBody(physicsSimulator, 15f, 1f);
            _parent.IsStatic = true;
            _parent.Position = new Vector2(xSeedPosition, ySeedPosition);
            AddCircleToCanvas(_parent, 15f);
            _parent.Tag = "D:" + item.ObjectPath;
            worldBodies.Add(_parent);
            bodies.Add(_parent);
            SetGravity(bodies);

            foreach (var parent in parents)
            {
                CreateSpring(_parent, parent);
            }
        }

        private void SetGravity(List<Body> bodies)
        {
            ComplexFactory.Instance.CreateGravityController(physicsSimulator, bodies, -1000f, 70f);
        }

        private Body CreateFileBody(Body parent, SVNObject item)
        {
            var body = BodyFactory.Instance.CreateCircleBody(physicsSimulator, 5f, 1f);
            var centre = new Vector2(0, 0);
            body.Tag = "F:" + item.ObjectPath;
            body.Position = new Vector2(800, 600);
            worldBodies.Add(body);
            var visual = AddCircleToCanvas(body, 5f);
            frameworkBodyItems.Add(body.Tag.ToString(), visual);
            ////SliderJoint pinJoint = JointFactory.Instance.CreateSliderJoint(physicsSimulator, parent, centre, body, centre, 15f, 20f);
            ////pinJoint.Tag = parent.Tag.ToString() + ":" + body.Tag.ToString();
            return body;
        }

        private Body CreateDirectoryBodySpringAndGravity(Body _body, SVNObject svnObject)
        {
            List<Body> bodies = new List<Body>();
            var centre = new Vector2(0, 0);
            var body = BodyFactory.Instance.CreateCircleBody(physicsSimulator, 15f, 1f);
            var geom = GeomFactory.Instance.CreateCircleGeom(physicsSimulator, body, 15f, 16);
            geom.CollisionCategories = CollisionCategory.Cat1;
            geom.Tag = "D:" + svnObject.ObjectPath;
            float rnd = GenerateRandomNumber();
            float X = _body.Position.X + rnd;

            rnd = GenerateRandomNumber();
            if (rnd == 0)
            {
                rnd++;
            }

            float Y = _body.Position.Y - 30f;
            body.Position = new Vector2(X, Y);
            while (worldBodies.Any(x => x.Tag.ToString().StartsWith("D:") && x.Position == body.Position))
            {
                body.Position = new Vector2(X + GenerateRandomNumber(), Y + GenerateRandomNumber());
            }

            body.Tag = "D:" + svnObject.ObjectPath;
            Debug.WriteLineIf(svnObject.ObjectPath == "/WorkHorse.Dto/TreeView", "Add Body");
            bodies.Add(body);
            Debug.WriteLineIf(svnObject.ObjectPath == "/WorkHorse.Dto/TreeView", "Create Spring");
            CreateSpring(_body, body);
            Debug.WriteLineIf(svnObject.ObjectPath == "/WorkHorse.Dto/TreeView", "Create SliderJoint");
            SliderJoint joint = JointFactory.Instance.CreateSliderJoint(physicsSimulator, _body, centre, body, centre, 60f, 120f);
            ////joint.TargetDistance = 60f;
            joint.Tag = _body.Tag.ToString() + ":" + body.Tag.ToString();
            /*FarseerGames.FarseerPhysics.Dynamics.Path path = ComplexFactory.Instance.CreateChain(physicsSimulator, _body.Position, body.Position, 5, 1, LinkType.PinJoint);
            foreach (var pathBody in path.Bodies)
            {
                pathBody.Tag = "P:";
            }

            foreach (var pathJoint in path.Joints)
            {
                pathJoint.Tag = "J:";
            }*/
            Debug.WriteLineIf(svnObject.ObjectPath == "/WorkHorse.Dto/TreeView", "Create Circle Visual");
            var circleVisual = AddCircleToCanvas(body, 15f);
            Debug.WriteLineIf(svnObject.ObjectPath == "/WorkHorse.Dto/TreeView", "Create Circle Visual Item");
            var bodyTag = body.Tag.ToString();
            if (frameworkBodyItems.ContainsKey(bodyTag))
            {
                frameworkBodyItems.Remove(bodyTag);
            }
            frameworkBodyItems.Add(body.Tag.ToString(), circleVisual);
            SetGravity(bodies);
            return body;
        }

        private void CreateSpring(Body _parent, Body body)
        {
            var centre = new Vector2(0, 0);
            //FarseerGames.FarseerPhysics.Dynamics.Path path = ComplexFactory.Instance.CreateChain(physicsSimulator, _parent.Position, body.Position, 5, 1f, LinkType.PinJoint);

            /*foreach (var item in path.Bodies)
            {
                item.Tag = "Rope";
            }*/

            /*foreach (var item in path.Joints)
            {
                item.Tag = "RopeJoint";
            }*/
            var springName = _parent.Tag.ToString() + ":" + body.Tag.ToString();
            //var existingJoint = physicsSimulator.SpringList.Where(x => x.Tag.ToString() == springName).SingleOrDefault();
            /*if (existingJoint != null)
            {
                existingJoint.IsDisposed = true;
                existingJoint.Dispose();
            }*/
            Debug.WriteLine(springName, "SpringName");
            var spring = SpringFactory.Instance.CreateLinearSpring(physicsSimulator, _parent, centre, body, centre, 1f, 1f);
            spring.Tag = springName;
            Debug.WriteLine(springName, "SpringVisual");
            var springVisual = AddLinearSpringVisualToCanvas(spring);
            Debug.WriteLine(springName, "SpringVisualToItems");
            if (frameworkSpringItems.ContainsKey(springName))
            {
                frameworkSpringItems.Remove(springName);
            }
            frameworkSpringItems.Add(springName, springVisual);

        }

        private float GenerateRandomNumber()
        {
            float rnd = (float)random.Next(-30, 30);
            if (rnd == 0)
            {
                rnd++;
            }
            return rnd;
        }

        private bool SVNObjectExists(SVNObject item, string type)
        {
            bool exists = false;
            foreach (var body in physicsSimulator.BodyList.Where(x => x.Tag != null))
            {
                if (body.Tag.ToString() == type + item.ObjectPath)
                {
                    exists = true;
                    break;
                }
            }
            return exists;
        }

        public bool IsPaused
        {
            get { return isPaused; }
            set { isPaused = value; }
        }

        public float ScreenWidth
        {
            get
            {
                return (float)ActualWidth;
            }
        }

        public float ScreenHeight
        {
            get
            {
                return (float)ActualHeight;
            }
        }

        public Vector2 ScreenCenter
        {
            get
            {
                return new Vector2(ScreenWidth / 2, ScreenHeight / 2);
            }
        }

        protected void ClearCanvas()
        {
            Children.Clear();
        }

        #region add visuals to canvas

        public Rectangle AddRectangleToCanvas(Body body, Vector2 size)
        {
            return AddRectangleToCanvas(body, Colors.Yellow, size);
        }

        public Rectangle AddRectangleToCanvas(Body body, Color color, Vector2 size)
        {
            Rectangle rectangle = new Rectangle();
            rectangle.StrokeThickness = 2;
            rectangle.Stroke = Brushes.Black;
            rectangle.Fill = new SolidColorBrush(color);
            rectangle.Width = size.X;
            rectangle.Height = size.Y;

            AddVisualToCanvas(rectangle, body);

            return rectangle;
        }



        public Ellipse AddCircleToCanvas(Body body, float radius)
        {
            return AddEllipseToCanvas(body, Colors.White, radius, radius);
        }

        public Ellipse AddCircleToCanvas(Body body, Color color, float radius)
        {
            return AddEllipseToCanvas(body, color, radius, radius);
        }

        public Ellipse AddEllipseToCanvas(Body body, float radiusX, float radiusY)
        {
            return AddEllipseToCanvas(body, Colors.White, radiusX, radiusY);
        }

        public Ellipse AddEllipseToCanvas(Body body, Color color, float radiusX, float radiusY)
        {
            Ellipse ellipse = new Ellipse();
            ellipse.StrokeThickness = 2;
            ellipse.Stroke = Brushes.Black;
            ellipse.Fill = new SolidColorBrush(color);
            ellipse.Width = radiusX * 2;
            ellipse.Height = radiusY * 2;

            AddVisualToCanvas(ellipse, body);

            return ellipse;
        }

        public AgentVisual AddAgentToCanvas(Body body)
        {
            AgentVisual agent = new AgentVisual();

            AddVisualToCanvas(agent, body);

            return agent;
        }

        public FixedLinearSpringVisual AddLinearSpringVisualToCanvas(LinearSpring spring)
        {
            FixedLinearSpringVisual visual = new FixedLinearSpringVisual();

            if (spring != null)
            {
                new FixedLinearSpringVisualHelper(visual, spring);
            }

            AddVisualToCanvas(visual, null);

            return visual;
        }

        public FixedLinearSpringVisual AddFixedLinearSpringVisualToCanvas(FixedLinearSpring spring)
        {
            FixedLinearSpringVisual visual = new FixedLinearSpringVisual();

            if (spring != null)
            {
                new FixedLinearSpringVisualHelper(visual, spring);
            }

            AddVisualToCanvas(visual, null);

            return visual;
        }

        private void RemoveSpringVisual(LinearSpring spring)
        {
        }

        private void RemoveVisual(FrameworkElement visual)
        {
            Children.Remove(visual);
        }

        public void AddVisualToCanvas(FrameworkElement visual, Body body)
        {
            if (body != null)
            {
                new BodyVisualHelper(visual, body);
            }

            //hasn't been rendered yet, so needs to work out it's size
            if (visual.ActualWidth == 0 && visual.ActualHeight == 0)
            {
                visual.Arrange(new Rect(0, 0, ActualWidth, ActualHeight));
            }

            Children.Add(visual);

            visual.IsHitTestVisible = true;
        }

        #endregion

        public static void CenterAround(FrameworkElement visual, Vector2 position)
        {
            SetLeft(visual, position.X - (visual.ActualWidth / 2));
            SetTop(visual, position.Y - (visual.ActualHeight / 2));
        }

        public static void PositionTopLeft(FrameworkElement visual, Vector2 position)
        {
            SetLeft(visual, position.X);
            SetTop(visual, position.Y);
        }

        public static void SetRotation(FrameworkElement visual, float radians)
        {
            visual.RenderTransform = new RotateTransform((radians * 360) / (2 * Math.PI), visual.ActualWidth / 2, visual.ActualHeight / 2);
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);

            Vector2 point = new Vector2((float)(e.GetPosition(this).X), (float)(e.GetPosition(this).Y));
            var pickedGeom = physicsSimulator.Collide(point);
            if (pickedGeom != null)
            {
                this.identity.Update(pickedGeom.Tag.ToString());
            }
        }
    }
}
